//! 中国象棋的实现

#![allow(dead_code)]

use std::io::{BufRead, BufReader};

// 这里演示了基本的使用方式，目前使用控制台来操作，后续可以套一个UI界面
fn main() {
    // 创建一个新的游戏
    let mut game = Game::new();

    // 下棋，直到游戏结束
    while let GameState::Play = game.state() {
        display(&game);

        while let Some((from, to)) = read_point() {
            // 移动棋子
            if let Err(e) = game.command(from, to) {
                eprintln!("command error: {e}"); // 打印命令的错误信息
            } else {
                break;
            }
        }
    }

    // 显示游戏赢家
    if let GameState::Over(player) = game.state() {
        let player = match player {
            Player::R => "R",
            Player::B => "B",
        };
        println!("Win {player}");
    }
}

/// 将游戏绘制到控制台
fn display(game: &Game) {
    let active = match game.active() {
        Player::R => "\x1B[31mActive R\x1B[0m",
        Player::B => "\x1B[34mActive B\x1B[0m",
    };
    println!("\x1B[32m ABCDEFGHI\x1B[0m ({active})");
    for (n, row) in game.view().into_iter().enumerate() {
        print!("\x1B[32m{n}\x1B[0m");
        for element in row {
            match element {
                Some((player, piece)) => {
                    let c = match (player, piece) {
                        (Player::R, Piece::K) => "\x1B[31mK\x1B[0m",
                        (Player::R, Piece::A) => "\x1B[31mA\x1B[0m",
                        (Player::R, Piece::E) => "\x1B[31mE\x1B[0m",
                        (Player::R, Piece::H) => "\x1B[31mH\x1B[0m",
                        (Player::R, Piece::R) => "\x1B[31mR\x1B[0m",
                        (Player::R, Piece::C) => "\x1B[31mC\x1B[0m",
                        (Player::R, Piece::P) => "\x1B[31mP\x1B[0m",
                        (Player::B, Piece::K) => "\x1B[34mK\x1B[0m",
                        (Player::B, Piece::A) => "\x1B[34mA\x1B[0m",
                        (Player::B, Piece::E) => "\x1B[34mE\x1B[0m",
                        (Player::B, Piece::H) => "\x1B[34mH\x1B[0m",
                        (Player::B, Piece::R) => "\x1B[34mR\x1B[0m",
                        (Player::B, Piece::C) => "\x1B[34mC\x1B[0m",
                        (Player::B, Piece::P) => "\x1B[34mP\x1B[0m",
                    };
                    print!("{c}")
                }
                None => print!("\x1B[90m+\x1B[0m"),
            }
        }
        println!()
    }
}

/// 获取用户输入的坐标点
fn read_point() -> Option<(Point, Point)> {
    let mut buf = String::new();

    BufReader::new(std::io::stdin())
        .read_line(&mut buf)
        .unwrap();

    let x1 = buf.as_bytes().get(0).and_then(|v| v.checked_sub(b'A'))?;
    let y1 = buf.as_bytes().get(1).and_then(|v| v.checked_sub(b'0'))?;
    let x2 = buf.as_bytes().get(2).and_then(|v| v.checked_sub(b'A'))?;
    let y2 = buf.as_bytes().get(3).and_then(|v| v.checked_sub(b'0'))?;

    Some((
        Point::new(i8::try_from(x1).ok()?, i8::try_from(y1).ok()?),
        Point::new(i8::try_from(x2).ok()?, i8::try_from(y2).ok()?),
    ))
}

// -----------------------------------------------------------------------------
// 上面是游戏的使用部分
// -----------------------------------------------------------------------------
// 下面是游戏的实现部分
// -----------------------------------------------------------------------------

/// 玩家
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Player {
    /// 红方
    R,
    /// 黑方
    B,
}

impl Player {
    pub fn opposite(self) -> Self {
        match self {
            Player::R => Player::B,
            Player::B => Player::R,
        }
    }
}

/// 棋子
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Piece {
    /// 帅 | 将
    K,
    /// 仕 | 士
    A,
    /// 相 | 象
    E,
    /// 马 | 马
    H,
    /// 车 | 车
    R,
    /// 炮 | 炮
    C,
    /// 兵 | 卒
    P,
}

/// 坐标点
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Point {
    pub x: i8,
    pub y: i8,
}

impl Point {
    pub fn new(x: i8, y: i8) -> Self {
        Self { x, y }
    }

    pub fn x_add(self, n: i8) -> Self {
        Self {
            x: self.x + n,
            y: self.y,
        }
    }

    pub fn x_sub(self, n: i8) -> Self {
        Self {
            x: self.x - n,
            y: self.y,
        }
    }

    pub fn y_add(self, n: i8) -> Self {
        Self {
            x: self.x,
            y: self.y + n,
        }
    }

    pub fn y_sub(self, n: i8) -> Self {
        Self {
            x: self.x,
            y: self.y - n,
        }
    }
}

impl std::fmt::Display for Point {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

/// 记录
#[derive(Debug, Clone)]
pub struct Record {
    pub from: Point,
    pub to: Point,
    pub element: Option<(Player, Piece)>,
}

/// 游戏状态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum GameState {
    /// 开始
    Play,
    /// 结束
    Over(Player),
}

/// 游戏
pub struct Game {
    /// 游戏地图
    map: [[Option<(Player, Piece)>; Game::MAP_W as usize]; Game::MAP_H as usize],
    /// 游戏记录
    record: Vec<Record>,
    /// 选中坐标
    select: Option<Point>,
    /// 执手玩家
    active: Player,
    /// 游戏状态
    state: GameState,
}

impl Game {
    /// 地图宽度
    pub const MAP_W: i8 = 9;
    /// 地图高度
    pub const MAP_H: i8 = 10;

    /// 地图田字格左边界
    pub const MAP_TZG_XL: i8 = 3;
    /// 地图田字格右边界
    pub const MAP_TZG_XR: i8 = 5;

    /// 地图田字格红方上边界
    pub const MAP_TZG_R_YU: i8 = 7;
    /// 地图田字格黑方下边界
    pub const MAP_TZG_B_YD: i8 = 2;

    /// 地图红方分界线
    pub const MAP_BOUNDARY_R: i8 = 5;
    /// 地图黑方分界线
    pub const MAP_BOUNDARY_B: i8 = 4;

    /// 初始地图
    pub const INITIAL_MAP: [[Option<(Player, Piece)>; Game::MAP_W as usize]; Game::MAP_H as usize] = [
        [
            Some((Player::B, Piece::R)),
            Some((Player::B, Piece::H)),
            Some((Player::B, Piece::E)),
            Some((Player::B, Piece::A)),
            Some((Player::B, Piece::K)),
            Some((Player::B, Piece::A)),
            Some((Player::B, Piece::E)),
            Some((Player::B, Piece::H)),
            Some((Player::B, Piece::R)),
        ],
        [None, None, None, None, None, None, None, None, None],
        [
            None,
            Some((Player::B, Piece::C)),
            None,
            None,
            None,
            None,
            None,
            Some((Player::B, Piece::C)),
            None,
        ],
        [
            Some((Player::B, Piece::P)),
            None,
            Some((Player::B, Piece::P)),
            None,
            Some((Player::B, Piece::P)),
            None,
            Some((Player::B, Piece::P)),
            None,
            Some((Player::B, Piece::P)),
        ],
        [None, None, None, None, None, None, None, None, None],
        [None, None, None, None, None, None, None, None, None],
        [
            Some((Player::R, Piece::P)),
            None,
            Some((Player::R, Piece::P)),
            None,
            Some((Player::R, Piece::P)),
            None,
            Some((Player::R, Piece::P)),
            None,
            Some((Player::R, Piece::P)),
        ],
        [
            None,
            Some((Player::R, Piece::C)),
            None,
            None,
            None,
            None,
            None,
            Some((Player::R, Piece::C)),
            None,
        ],
        [None, None, None, None, None, None, None, None, None],
        [
            Some((Player::R, Piece::R)),
            Some((Player::R, Piece::H)),
            Some((Player::R, Piece::E)),
            Some((Player::R, Piece::A)),
            Some((Player::R, Piece::K)),
            Some((Player::R, Piece::A)),
            Some((Player::R, Piece::E)),
            Some((Player::R, Piece::H)),
            Some((Player::R, Piece::R)),
        ],
    ];

    /// 创建一个新的游戏
    pub fn new() -> Self {
        Self {
            map: Game::INITIAL_MAP,
            record: vec![],
            select: None,
            active: Player::R,
            state: GameState::Play,
        }
    }

    /// 获取游戏状态
    pub fn state(&self) -> GameState {
        self.state
    }

    /// 获取执手玩家
    pub fn active(&self) -> Player {
        self.active
    }

    /// 游戏视图
    pub fn view(&self) -> &[[Option<(Player, Piece)>; Game::MAP_W as usize]; Game::MAP_H as usize] {
        &self.map
    }

    /// 移动棋子
    pub fn command(&mut self, from: Point, to: Point) -> Result<(), GameCommandError> {
        if let GameState::Over(_) = self.state {
            return Ok(());
        }

        if Game::is_invalid_point(from) {
            return Err(GameCommandError::InvalidPoint(from));
        }
        if Game::is_invalid_point(to) {
            return Err(GameCommandError::InvalidPoint(to));
        }

        let Some((player, _)) = self.map_element(from) else {
            return Err(GameCommandError::NoPieceSelected);
        };

        if self.active != player {
            return Err(GameCommandError::NoPieceSelected);
        }

        // 判断是否可以移动棋子
        if !self.actionable_points(from).contains(&to) {
            return Err(GameCommandError::UnableToMovePiece);
        }

        // 移动棋子
        let element = self.map_element_mut(from).take().unwrap();
        let element = self.map_element_mut(to).replace(element);

        // 记录步骤
        self.record.push(Record { from, to, element });

        // 游戏结束
        if let Some((_, Piece::K)) = element {
            self.state = GameState::Over(self.active);
        }

        // 切换执手玩家
        self.active = self.active.opposite();

        Ok(())
    }

    /// 获取棋子可以行动的坐标点
    pub fn actionable_points(&self, point: Point) -> Vec<Point> {
        if Game::is_invalid_point(point) {
            return vec![];
        }

        let Some((player, piece)) = self.map_element(point) else {
            return vec![];
        };

        match piece {
            Piece::K => {
                let candidate = [
                    point.x_add(1),
                    point.x_sub(1),
                    point.y_add(1),
                    point.y_sub(1),
                ];
                candidate
                    .into_iter()
                    // 过滤棋盘外的坐标点
                    .filter(|p| !Game::is_invalid_point(*p))
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|p| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    // 过滤横坐标在田字格外的坐标点
                    .filter(|p| p.x >= Game::MAP_TZG_XL && p.x <= Game::MAP_TZG_XR)
                    // 过滤纵坐标在田字格外的坐标点
                    .filter(|p| match player {
                        Player::R => p.y >= Game::MAP_TZG_R_YU,
                        Player::B => p.y <= Game::MAP_TZG_B_YD,
                    })
                    .collect()
            }
            Piece::A => {
                let candidate = [
                    point.x_add(1).y_add(1),
                    point.x_add(1).y_sub(1),
                    point.x_sub(1).y_add(1),
                    point.x_sub(1).y_sub(1),
                ];
                candidate
                    .into_iter()
                    // 过滤棋盘外的坐标点
                    .filter(|p| !Game::is_invalid_point(*p))
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|p| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    // 过滤横坐标在田字格外的坐标点
                    .filter(|p| p.x >= Game::MAP_TZG_XL && p.x <= Game::MAP_TZG_XR)
                    // 过滤纵坐标在田字格外的坐标点
                    .filter(|p| match player {
                        Player::R => p.y >= Game::MAP_TZG_R_YU,
                        Player::B => p.y <= Game::MAP_TZG_B_YD,
                    })
                    .collect()
            }
            Piece::E => {
                let candidate = [
                    point.x_add(2).y_add(2),
                    point.x_add(2).y_sub(2),
                    point.x_sub(2).y_add(2),
                    point.x_sub(2).y_sub(2),
                ];
                candidate
                    .into_iter()
                    // 过滤棋盘外的坐标点
                    .filter(|p| !Game::is_invalid_point(*p))
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|p| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    // 过滤过河的坐标点
                    .filter(|p| match player {
                        Player::R => p.y >= Game::MAP_BOUNDARY_R,
                        Player::B => p.y <= Game::MAP_BOUNDARY_B,
                    })
                    // 过滤塞象眼的坐标点
                    .filter(|p| {
                        let x = (p.x + point.x) / 2;
                        let y = (p.y + point.y) / 2;
                        self.map_element(Point::new(x, y)).is_none()
                    })
                    .collect()
            }
            Piece::H => {
                let candidate = [
                    (point.x_add(2).y_add(1), point.x_add(1)),
                    (point.x_add(2).y_sub(1), point.x_add(1)),
                    (point.x_sub(2).y_add(1), point.x_sub(1)),
                    (point.x_sub(2).y_sub(1), point.x_sub(1)),
                    (point.y_add(2).x_add(1), point.y_add(1)),
                    (point.y_add(2).x_sub(1), point.y_add(1)),
                    (point.y_sub(2).x_add(1), point.y_sub(1)),
                    (point.y_sub(2).x_sub(1), point.y_sub(1)),
                ];
                candidate
                    .into_iter()
                    // 过滤棋盘外的坐标点
                    .filter(|(p, _)| !Game::is_invalid_point(*p))
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|(p, _)| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    // 过滤蹩马腿的坐标点
                    .filter(|(_, p)| self.map_element(*p).is_none())
                    .map(|(p, _)| p)
                    .collect()
            }
            Piece::R => {
                let (min_x, max_x) = self.x_collision(point);
                let (min_y, max_y) = self.y_collision(point);

                let x_points = (min_x.unwrap_or(0)..=max_x.unwrap_or(Game::MAP_W - 1))
                    .map(|x| Point::new(x, point.y));
                let y_points = (min_y.unwrap_or(0)..=max_y.unwrap_or(Game::MAP_H - 1))
                    .map(|y| Point::new(point.x, y));
                let candidate = x_points.chain(y_points);

                candidate
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|p| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    .collect()
            }
            Piece::C => {
                let (min_x, max_x) = self.x_collision(point);
                let (min_y, max_y) = self.y_collision(point);

                let x1 = min_x.and_then(|x| self.x_l_collision(Point::new(x, point.y)));
                let x2 = max_x.and_then(|x| self.x_r_collision(Point::new(x, point.y)));
                let y1 = min_y.and_then(|y| self.y_u_collision(Point::new(point.x, y)));
                let y2 = max_y.and_then(|y| self.y_d_collision(Point::new(point.x, y)));

                let p1 = x1.map(|x| Point::new(x, point.y));
                let p2 = x2.map(|x| Point::new(x, point.y));
                let p3 = y1.map(|y| Point::new(point.x, y));
                let p4 = y2.map(|y| Point::new(point.x, y));

                let can_eat_points = p1.into_iter().chain(p2).chain(p3).chain(p4).filter(|p| {
                    self.map_element(*p)
                        .map(|(p, _)| p != player)
                        .unwrap_or(true)
                });

                let x_points = (min_x.unwrap_or(0)..=max_x.unwrap_or(Game::MAP_W - 1))
                    .map(|x| Point::new(x, point.y));
                let y_points = (min_y.unwrap_or(0)..=max_y.unwrap_or(Game::MAP_H - 1))
                    .map(|y| Point::new(point.x, y));
                let candidate = x_points.chain(y_points);

                candidate
                    // 过滤含有棋子的坐标点
                    .filter(|p| self.map_element(*p).is_none())
                    // 添加炮可以吃的坐标点
                    .chain(can_eat_points)
                    .collect()
            }
            Piece::P => {
                let candidate = match player {
                    Player::R => [point.x_add(1), point.x_sub(1), point.y_sub(1)],
                    Player::B => [point.x_add(1), point.x_sub(1), point.y_add(1)],
                };
                candidate
                    .into_iter()
                    // 过滤棋盘外的坐标点
                    .filter(|p| !Game::is_invalid_point(*p))
                    // 过滤未过河左右移动的坐标点
                    .filter(|p| match player {
                        Player::R if point.y >= Game::MAP_BOUNDARY_R => point.x == p.x,
                        Player::B if point.y <= Game::MAP_BOUNDARY_B => point.x == p.x,
                        _ => true,
                    })
                    // 过滤含有己方阵营棋子的坐标点
                    .filter(|p| {
                        self.map_element(*p)
                            .map(|(p, _)| p != player)
                            .unwrap_or(true)
                    })
                    .collect()
            }
        }
    }

    /// 获取指定坐标点的左右碰撞点
    fn x_collision(&self, point: Point) -> (Option<i8>, Option<i8>) {
        let min_x = self.x_l_collision(point);
        let max_x = self.x_r_collision(point);
        (min_x, max_x)
    }

    /// 获取指定坐标点的左碰撞点
    fn x_l_collision(&self, point: Point) -> Option<i8> {
        let mut x = point.x - 1;
        loop {
            if x < 0 {
                break None;
            }
            if self.map_element(Point::new(x, point.y)).is_some() {
                break Some(x);
            }
            x -= 1;
        }
    }

    /// 获取指定坐标点的右碰撞点
    fn x_r_collision(&self, point: Point) -> Option<i8> {
        let mut x = point.x + 1;
        loop {
            if x >= Game::MAP_W {
                break None;
            }
            if self.map_element(Point::new(x, point.y)).is_some() {
                break Some(x);
            }
            x += 1;
        }
    }

    /// 获取指定坐标点的上下碰撞点
    fn y_collision(&self, point: Point) -> (Option<i8>, Option<i8>) {
        let min_y = self.y_u_collision(point);
        let max_y = self.y_d_collision(point);
        (min_y, max_y)
    }

    /// 获取指定坐标点的上碰撞点
    fn y_u_collision(&self, point: Point) -> Option<i8> {
        let mut y = point.y - 1;
        loop {
            if y < 0 {
                break None;
            }
            if self.map_element(Point::new(point.x, y)).is_some() {
                break Some(y);
            }
            y -= 1;
        }
    }

    /// 获取指定坐标点的下碰撞点
    fn y_d_collision(&self, point: Point) -> Option<i8> {
        let mut y = point.y + 1;
        loop {
            if y >= Game::MAP_H {
                break None;
            }
            if self.map_element(Point::new(point.x, y)).is_some() {
                break Some(y);
            }
            y += 1;
        }
    }

    /// 获取地图元素
    fn map_element(&self, point: Point) -> Option<(Player, Piece)> {
        self.map[point.y as usize][point.x as usize]
    }

    /// 获取地图元素
    fn map_element_ref(&self, point: Point) -> &Option<(Player, Piece)> {
        &self.map[point.y as usize][point.x as usize]
    }

    /// 获取地图元素
    fn map_element_mut(&mut self, point: Point) -> &mut Option<(Player, Piece)> {
        &mut self.map[point.y as usize][point.x as usize]
    }

    /// 无效的坐标点
    #[rustfmt::skip]
    fn is_invalid_point(point: Point) -> bool {
        point.x < 0 || point.x >= Game::MAP_W ||
        point.y < 0 || point.y >= Game::MAP_H
    }
}

/// 游戏命令错误
#[derive(Debug)]
pub enum GameCommandError {
    /// 无效的坐标点
    InvalidPoint(Point),
    /// 无法移动棋子
    UnableToMovePiece,
    /// 没有选中棋子
    NoPieceSelected,
}

impl std::fmt::Display for GameCommandError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GameCommandError::InvalidPoint(point) => write!(f, "invalid point {point}"),
            GameCommandError::UnableToMovePiece => write!(f, "unable to move piece"),
            GameCommandError::NoPieceSelected => write!(f, "no piece selected"),
        }
    }
}

impl std::error::Error for GameCommandError {}
